/*! Jcrop.js v2.0.4 - build: 20151117
 *  @copyright 2008-2015 Tapmodo Interactive LLC
 *  @license Free software under MIT License
 *  @website http://jcrop.org/
 **/
(function($) {
    "use strict";

    // Jcrop constructor
    var Jcrop = function(element, opt) {
        var _ua = navigator.userAgent.toLowerCase();

        this.opt = $.extend({}, Jcrop.defaults, opt || {});

        this.container = $(element);

        this.opt.is_msie = /msie/.test(_ua);
        this.opt.is_ie_lt9 = /msie [1-8]\./.test(_ua);

        this.container.addClass(this.opt.css_container);

        this.ui = {};
        this.state = null;
        this.ui.multi = [];
        this.ui.selection = null;
        this.filter = {};

        this.init();
        this.setOptions(opt);
        this.applySizeConstraints();
        this.container.trigger("cropinit", this);

        // IE<9 doesn't work if mouse events are attached to window
        if (this.opt.is_ie_lt9)
            this.opt.dragEventTarget = document.body;
    };


    // Jcrop static functions
    $.extend(Jcrop,
        {
            component: {},
            filter: {},
            stage: {},
            registerComponent: function(name, component) {
                Jcrop.component[name] = component;
            },
            registerFilter: function(name, filter) {
                Jcrop.filter[name] = filter;
            },
            registerStageType: function(name, stage) {
                Jcrop.stage[name] = stage;
            },
            // attach: function(element,opt){{{
            attach: function(element, opt) {
                var obj = new $.Jcrop(element, opt);
                return obj;
            },
            // }}}
            // imgCopy: function(imgel){{{
            imgCopy: function(imgel) {
                var img = new Image;
                img.src = imgel.src;
                return img;
            },
            // }}}
            // imageClone: function(imgel){{{
            imageClone: function(imgel) {
                return $.Jcrop.supportsCanvas ? Jcrop.canvasClone(imgel) : Jcrop.imgCopy(imgel);
            },
            // }}}
            // canvasClone: function(imgel){{{
            canvasClone: function(imgel) {
                var canvas = document.createElement("canvas"),
                    ctx = canvas.getContext("2d");

                $(canvas).width(imgel.width).height(imgel.height),
                    canvas.width = imgel.naturalWidth;
                canvas.height = imgel.naturalHeight;
                ctx.drawImage(imgel, 0, 0, imgel.naturalWidth, imgel.naturalHeight);
                return canvas;
            },
            // }}}
            // propagate: function(plist,config,obj){{{
            propagate: function(plist, config, obj) {
                for (var i = 0, l = plist.length; i < l; i++)
                    if (config.hasOwnProperty(plist[i]))
                        obj[plist[i]] = config[plist[i]];
            },
            // }}}
            // getLargestBox: function(ratio,w,h){{{
            getLargestBox: function(ratio, w, h) {
                if ((w / h) > ratio)
                    return [h * ratio, h];
                else return [w, w / ratio];
            },
            // }}}
            // stageConstructor: function(el,options,callback){{{
            stageConstructor: function(el, options, callback) {

                // Get a priority-ordered list of available stages
                var stages = [];
                $.each(Jcrop.stage,
                    function(i, e) {
                        stages.push(e);
                    });
                stages.sort(function(a, b) { return a.priority - b.priority; });

                // Find the first one that supports this element
                for (var i = 0, l = stages.length; i < l; i++) {
                    if (stages[i].isSupported(el, options)) {
                        stages[i].create(el,
                            options,
                            function(obj, opt) {
                                if (typeof callback == "function") callback(obj, opt);
                            });
                        break;
                    }
                }
            },
            // }}}
            // supportsColorFade: function(){{{
            supportsColorFade: function() {
                return $.fx.step.hasOwnProperty("backgroundColor");
            },
            // }}}
            // wrapFromXywh: function(xywh){{{
            wrapFromXywh: function(xywh) {
                var b = { x: xywh[0], y: xywh[1], w: xywh[2], h: xywh[3] };
                b.x2 = b.x + b.w;
                b.y2 = b.y + b.h;
                return b;
            }
            // }}}
        });

    var AbstractStage = function() {
    };

    $.extend(AbstractStage,
        {
            isSupported: function(el, o) {
                // @todo: should actually check if it's an HTML element
                return true;
            },
            // A higher priority means less desirable
            // AbstractStage is the last one we want to use
            priority: 100,
            create: function(el, options, callback) {
                var obj = new AbstractStage;
                obj.element = el;
                callback.call(this, obj, options);
            },
            prototype: {
                attach: function(core) {
                    this.init(core);
                    core.ui.stage = this;
                },
                triggerEvent: function(ev) {
                    $(this.element).trigger(ev);
                    return this;
                },
                getElement: function() {
                    return this.element;
                }
            }
        });
    Jcrop.registerStageType("Block", AbstractStage);


    var ImageStage = function() {
    };

    ImageStage.prototype = new AbstractStage();

    $.extend(ImageStage,
        {
            isSupported: function(el, o) {
                if (el.tagName == "IMG") return true;
            },
            priority: 90,
            create: function(el, options, callback) {
                $.Jcrop.component.ImageLoader.attach(el,
                    function(w, h) {
                        var obj = new ImageStage;
                        obj.element = $(el).wrap("<div />").parent();

                        obj.element.width(w).height(h);
                        obj.imgsrc = el;

                        if (typeof callback == "function")
                            callback.call(this, obj, options);
                    });
            }
        });
    Jcrop.registerStageType("Image", ImageStage);


    var CanvasStage = function() {
        this.angle = 0;
        this.scale = 1;
        this.scaleMin = 0.2;
        this.scaleMax = 1.25;
        this.offset = [0, 0];
    };

    CanvasStage.prototype = new ImageStage();

    $.extend(CanvasStage,
        {
            isSupported: function(el, o) {
                if ($.Jcrop.supportsCanvas && (el.tagName == "IMG")) return true;
            },
            priority: 60,
            create: function(el, options, callback) {
                var $el = $(el);
                var opt = $.extend({}, options);
                $.Jcrop.component.ImageLoader.attach(el,
                    function(w, h) {
                        var obj = new CanvasStage;
                        $el.hide();
                        obj.createCanvas(el, w, h);
                        $el.before(obj.element);
                        obj.imgsrc = el;
                        opt.imgsrc = el;

                        if (typeof callback == "function") {
                            callback(obj, opt);
                            obj.redraw();
                        }
                    });
            }
        });

    $.extend(CanvasStage.prototype,
        {
            init: function(core) {
                this.core = core;
            },
            // setOffset: function(x,y) {{{
            setOffset: function(x, y) {
                this.offset = [x, y];
                return this;
            },
            // }}}
            // setAngle: function(v) {{{
            setAngle: function(v) {
                this.angle = v;
                return this;
            },
            // }}}
            // setScale: function(v) {{{
            setScale: function(v) {
                this.scale = this.boundScale(v);
                return this;
            },
            // }}}
            boundScale: function(v) {
                if (v < this.scaleMin) v = this.scaleMin;
                else if (v > this.scaleMax) v = this.scaleMax;
                return v;
            },
            createCanvas: function(img, w, h) {
                this.width = w;
                this.height = h;
                this.canvas = document.createElement("canvas");
                this.canvas.width = w;
                this.canvas.height = h;
                this.$canvas = $(this.canvas).width("100%").height("100%");
                this.context = this.canvas.getContext("2d");
                this.fillstyle = "rgb(0,0,0)";
                this.element = this.$canvas.wrap("<div />").parent().width(w).height(h);
            },
            triggerEvent: function(ev) {
                this.$canvas.trigger(ev);
                return this;
            },
            // clear: function() {{{
            clear: function() {
                this.context.fillStyle = this.fillstyle;
                this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
                return this;
            },
            // }}}
            // redraw: function() {{{
            redraw: function() {
                // Save the current context
                this.context.save();
                this.clear();

                // Translate to the center point of our image
                this.context.translate(parseInt(this.width * 0.5), parseInt(this.height * 0.5));
                // Perform the rotation and scaling
                this.context.translate(this.offset[0] / this.core.opt.xscale, this.offset[1] / this.core.opt.yscale);
                this.context.rotate(this.angle * (Math.PI / 180));
                this.context.scale(this.scale, this.scale);
                // Translate back to the top left of our image
                this.context.translate(-parseInt(this.width * 0.5), -parseInt(this.height * 0.5));
                // Finally we draw the image
                this.context.drawImage(this.imgsrc, 0, 0, this.width, this.height);

                // And restore the updated context
                this.context.restore();
                this.$canvas.trigger("cropredraw");
                return this;
            },
            // }}}
            // setFillStyle: function(v) {{{
            setFillStyle: function(v) {
                this.fillstyle = v;
                return this;
            }
            // }}}
        });

    Jcrop.registerStageType("Canvas", CanvasStage);


    /**
     *  BackoffFilter
     *  move out-of-bounds selection into allowed position at same size
     */
    var BackoffFilter = function() {
        this.minw = 40;
        this.minh = 40;
        this.maxw = 0;
        this.maxh = 0;
        this.core = null;
    };
    $.extend(BackoffFilter.prototype,
        {
            tag: "backoff",
            priority: 22,
            filter: function(b) {
                var r = this.bound;

                if (b.x < r.minx) {
                    b.x = r.minx;
                    b.x2 = b.w + b.x;
                }
                if (b.y < r.miny) {
                    b.y = r.miny;
                    b.y2 = b.h + b.y;
                }
                if (b.x2 > r.maxx) {
                    b.x2 = r.maxx;
                    b.x = b.x2 - b.w;
                }
                if (b.y2 > r.maxy) {
                    b.y2 = r.maxy;
                    b.y = b.y2 - b.h;
                }

                return b;
            },
            refresh: function(sel) {
                this.elw = sel.core.container.width();
                this.elh = sel.core.container.height();
                this.bound = {
                    minx: 0 + sel.edge.w,
                    miny: 0 + sel.edge.n,
                    maxx: this.elw + sel.edge.e,
                    maxy: this.elh + sel.edge.s
                };
            }
        });
    Jcrop.registerFilter("backoff", BackoffFilter);

    /**
     *  ConstrainFilter
     *  a filter to constrain crop selection to bounding element
     */
    var ConstrainFilter = function() {
        this.core = null;
    };
    $.extend(ConstrainFilter.prototype,
        {
            tag: "constrain",
            priority: 5,
            filter: function(b, ord) {
                if (ord == "move") {
                    if (b.x < this.minx) {
                        b.x = this.minx;
                        b.x2 = b.w + b.x;
                    }
                    if (b.y < this.miny) {
                        b.y = this.miny;
                        b.y2 = b.h + b.y;
                    }
                    if (b.x2 > this.maxx) {
                        b.x2 = this.maxx;
                        b.x = b.x2 - b.w;
                    }
                    if (b.y2 > this.maxy) {
                        b.y2 = this.maxy;
                        b.y = b.y2 - b.h;
                    }
                } else {
                    if (b.x < this.minx) {
                        b.x = this.minx;
                    }
                    if (b.y < this.miny) {
                        b.y = this.miny;
                    }
                    if (b.x2 > this.maxx) {
                        b.x2 = this.maxx;
                    }
                    if (b.y2 > this.maxy) {
                        b.y2 = this.maxy;
                    }
                }
                b.w = b.x2 - b.x;
                b.h = b.y2 - b.y;
                return b;
            },
            refresh: function(sel) {
                this.elw = sel.core.container.width();
                this.elh = sel.core.container.height();
                this.minx = 0 + sel.edge.w;
                this.miny = 0 + sel.edge.n;
                this.maxx = this.elw + sel.edge.e;
                this.maxy = this.elh + sel.edge.s;
            }
        });
    Jcrop.registerFilter("constrain", ConstrainFilter);

    /**
     *  ExtentFilter
     *  a filter to implement minimum or maximum size
     */
    var ExtentFilter = function() {
        this.core = null;
    };
    $.extend(ExtentFilter.prototype,
        {
            tag: "extent",
            priority: 12,
            offsetFromCorner: function(corner, box, b) {
                var w = box[0], h = box[1];
                switch (corner) {
                case "bl":
                    return [b.x2 - w, b.y, w, h];
                case "tl":
                    return [b.x2 - w, b.y2 - h, w, h];
                case "br":
                    return [b.x, b.y, w, h];
                case "tr":
                    return [b.x, b.y2 - h, w, h];
                }
            },
            getQuadrant: function(s) {
                var relx = s.opposite[0] - s.offsetx;
                var rely = s.opposite[1] - s.offsety;

                if ((relx < 0) && (rely < 0)) return "br";
                else if ((relx >= 0) && (rely >= 0)) return "tl";
                else if ((relx < 0) && (rely >= 0)) return "tr";
                return "bl";
            },
            filter: function(b, ord, sel) {

                if (ord == "move") return b;

                var w = b.w, h = b.h, st = sel.state, r = this.limits;
                var quad = st ? this.getQuadrant(st) : "br";

                if (r.minw && (w < r.minw)) w = r.minw;
                if (r.minh && (h < r.minh)) h = r.minh;
                if (r.maxw && (w > r.maxw)) w = r.maxw;
                if (r.maxh && (h > r.maxh)) h = r.maxh;

                if ((w == b.w) && (h == b.h)) return b;

                return Jcrop.wrapFromXywh(this.offsetFromCorner(quad, [w, h], b));
            },
            refresh: function(sel) {
                this.elw = sel.core.container.width();
                this.elh = sel.core.container.height();

                this.limits = {
                    minw: sel.minSize[0],
                    minh: sel.minSize[1],
                    maxw: sel.maxSize[0],
                    maxh: sel.maxSize[1]
                };
            }
        });
    Jcrop.registerFilter("extent", ExtentFilter);


    /**
     *  GridFilter
     *  a rudimentary grid effect
     */
    var GridFilter = function() {
        this.stepx = 1;
        this.stepy = 1;
        this.core = null;
    };
    $.extend(GridFilter.prototype,
        {
            tag: "grid",
            priority: 19,
            filter: function(b) {

                var n = {
                    x: Math.round(b.x / this.stepx) * this.stepx,
                    y: Math.round(b.y / this.stepy) * this.stepy,
                    x2: Math.round(b.x2 / this.stepx) * this.stepx,
                    y2: Math.round(b.y2 / this.stepy) * this.stepy
                };

                n.w = n.x2 - n.x;
                n.h = n.y2 - n.y;

                return n;
            }
        });
    Jcrop.registerFilter("grid", GridFilter);


    /**
     *  RatioFilter
     *  implements aspectRatio locking
     */
    var RatioFilter = function() {
        this.ratio = 0;
        this.core = null;
    };
    $.extend(RatioFilter.prototype,
        {
            tag: "ratio",
            priority: 15,
            offsetFromCorner: function(corner, box, b) {
                var w = box[0], h = box[1];
                switch (corner) {
                case "bl":
                    return [b.x2 - w, b.y, w, h];
                case "tl":
                    return [b.x2 - w, b.y2 - h, w, h];
                case "br":
                    return [b.x, b.y, w, h];
                case "tr":
                    return [b.x, b.y2 - h, w, h];
                }
            },
            getBoundRatio: function(b, quad) {
                var box = Jcrop.getLargestBox(this.ratio, b.w, b.h);
                return Jcrop.wrapFromXywh(this.offsetFromCorner(quad, box, b));
            },
            getQuadrant: function(s) {
                var relx = s.opposite[0] - s.offsetx;
                var rely = s.opposite[1] - s.offsety;

                if ((relx < 0) && (rely < 0)) return "br";
                else if ((relx >= 0) && (rely >= 0)) return "tl";
                else if ((relx < 0) && (rely >= 0)) return "tr";
                return "bl";
            },
            filter: function(b, ord, sel) {

                if (!this.ratio) return b;

                var rt = b.w / b.h;
                var st = sel.state;

                var quad = st ? this.getQuadrant(st) : "br";
                ord = ord || "se";

                if (ord == "move") return b;

                switch (ord) {
                case "n":
                    b.x2 = this.elw;
                    b.w = b.x2 - b.x;
                    quad = "tr";
                    break;
                case "s":
                    b.x2 = this.elw;
                    b.w = b.x2 - b.x;
                    quad = "br";
                    break;
                case "e":
                    b.y2 = this.elh;
                    b.h = b.y2 - b.y;
                    quad = "br";
                    break;
                case "w":
                    b.y2 = this.elh;
                    b.h = b.y2 - b.y;
                    quad = "bl";
                    break;
                }

                return this.getBoundRatio(b, quad);
            },
            refresh: function(sel) {
                this.ratio = sel.aspectRatio;
                this.elw = sel.core.container.width();
                this.elh = sel.core.container.height();
            }
        });
    Jcrop.registerFilter("ratio", RatioFilter);


    /**
     *  RoundFilter
     *  rounds coordinate values to integers
     */
    var RoundFilter = function() {
        this.core = null;
    };
    $.extend(RoundFilter.prototype,
        {
            tag: "round",
            priority: 90,
            filter: function(b) {

                var n = {
                    x: Math.round(b.x),
                    y: Math.round(b.y),
                    x2: Math.round(b.x2),
                    y2: Math.round(b.y2)
                };

                n.w = n.x2 - n.x;
                n.h = n.y2 - n.y;

                return n;
            }
        });
    Jcrop.registerFilter("round", RoundFilter);


    /**
     *  ShadeFilter
     *  A filter that implements div-based shading on any element
     *
     *  The shading you see is actually four semi-opaque divs
     *  positioned inside the container, around the selection
     */
    var ShadeFilter = function(opacity, color) {
        this.color = color || "black";
        this.opacity = opacity || 0.5;
        this.core = null;
        this.shades = {};
    };
    $.extend(ShadeFilter.prototype,
        {
            tag: "shader",
            fade: true,
            fadeEasing: "swing",
            fadeSpeed: 320,
            priority: 95,
            init: function() {
                var t = this;

                if (!t.attached) {
                    t.visible = false;

                    t.container = $("<div />").addClass(t.core.opt.css_shades)
                        .prependTo(this.core.container).hide();

                    t.elh = this.core.container.height();
                    t.elw = this.core.container.width();

                    t.shades = {
                        top: t.createShade(),
                        right: t.createShade(),
                        left: t.createShade(),
                        bottom: t.createShade()
                    };

                    t.attached = true;
                }
            },
            destroy: function() {
                this.container.remove();
            },
            setColor: function(color, instant) {
                var t = this;

                if (color == t.color) return t;

                this.color = color;
                var colorfade = Jcrop.supportsColorFade();
                $.each(t.shades,
                    function(u, i) {
                        if (!t.fade || instant || !colorfade) i.css("backgroundColor", color);
                        else
                            i.animate({ backgroundColor: color },
                                { queue: false, duration: t.fadeSpeed, easing: t.fadeEasing });
                    });
                return t;
            },
            setOpacity: function(opacity, instant) {
                var t = this;

                if (opacity == t.opacity) return t;

                t.opacity = opacity;
                $.each(t.shades,
                    function(u, i) {
                        if (!t.fade || instant) i.css({ opacity: opacity });
                        else
                            i.animate({ opacity: opacity },
                                { queue: false, duration: t.fadeSpeed, easing: t.fadeEasing });
                    });
                return t;
            },
            createShade: function() {
                return $("<div />").css({
                    position: "absolute",
                    backgroundColor: this.color,
                    opacity: this.opacity
                }).appendTo(this.container);
            },
            refresh: function(sel) {
                var m = this.core, s = this.shades;

                this.setColor(sel.bgColor ? sel.bgColor : this.core.opt.bgColor);
                this.setOpacity(sel.bgOpacity ? sel.bgOpacity : this.core.opt.bgOpacity);

                this.elh = m.container.height();
                this.elw = m.container.width();
                s.right.css("height", this.elh + "px");
                s.left.css("height", this.elh + "px");
            },
            filter: function(b, ord, sel) {

                if (!sel.active) return b;

                var t = this,
                    s = t.shades;

                s.top.css({
                    left: Math.round(b.x) + "px",
                    width: Math.round(b.w) + "px",
                    height: Math.round(b.y) + "px"
                });
                s.bottom.css({
                    top: Math.round(b.y2) + "px",
                    left: Math.round(b.x) + "px",
                    width: Math.round(b.w) + "px",
                    height: (t.elh - Math.round(b.y2)) + "px"
                });
                s.right.css({
                    left: Math.round(b.x2) + "px",
                    width: (t.elw - Math.round(b.x2)) + "px"
                });
                s.left.css({
                    width: Math.round(b.x) + "px"
                });

                if (!t.visible) {
                    t.container.show();
                    t.visible = true;
                }

                return b;
            }
        });
    Jcrop.registerFilter("shader", ShadeFilter);


    /**
     *  CanvasAnimator
     *  manages smooth cropping animation
     *
     *  This object is called internally to manage animation.
     *  An in-memory div is animated and a progress callback
     *  is used to update the selection coordinates of the
     *  visible selection in realtime.
     */
    var CanvasAnimator = function(stage) {
        this.stage = stage;
        this.core = stage.core;
        this.cloneStagePosition();
    };

    CanvasAnimator.prototype = {
        cloneStagePosition: function() {
            var s = this.stage;
            this.angle = s.angle;
            this.scale = s.scale;
            this.offset = s.offset;
        },

        getElement: function() {
            var s = this.stage;

            return $("<div />")
                .css({
                    position: "absolute",
                    top: s.offset[0] + "px",
                    left: s.offset[1] + "px",
                    width: s.angle + "px",
                    height: s.scale + "px"
                });
        },

        animate: function(cb) {
            var t = this;

            this.scale = this.stage.boundScale(this.scale);
            t.stage.triggerEvent("croprotstart");

            t.getElement().animate({
                    top: t.offset[0] + "px",
                    left: t.offset[1] + "px",
                    width: t.angle + "px",
                    height: t.scale + "px"
                },
                {
                    easing: t.core.opt.animEasing,
                    duration: t.core.opt.animDuration,
                    complete: function() {
                        t.stage.triggerEvent("croprotend");
                        (typeof cb == "function") && cb.call(this);
                    },
                    progress: function(anim) {
                        var props = {}, i, tw = anim.tweens;

                        for (i = 0; i < tw.length; i++) {
                            props[tw[i].prop] = tw[i].now;
                        }

                        t.stage.setAngle(props.width)
                            .setScale(props.height)
                            .setOffset(props.top, props.left)
                            .redraw();
                    }
                });
        }

    };
    Jcrop.stage.Canvas.prototype.getAnimator = function() {
        return new CanvasAnimator(this);
    };
    Jcrop.registerComponent("CanvasAnimator", CanvasAnimator);


    /**
     *  CropAnimator
     *  manages smooth cropping animation
     *
     *  This object is called internally to manage animation.
     *  An in-memory div is animated and a progress callback
     *  is used to update the selection coordinates of the
     *  visible selection in realtime.
     */
    // var CropAnimator = function(selection){{{
    var CropAnimator = function(selection) {
        this.selection = selection;
        this.core = selection.core;
    };
    // }}}

    CropAnimator.prototype = {
        getElement: function() {
            var b = this.selection.get();

            return $("<div />")
                .css({
                    position: "absolute",
                    top: b.y + "px",
                    left: b.x + "px",
                    width: b.w + "px",
                    height: b.h + "px"
                });
        },

        animate: function(x, y, w, h, cb) {
            var t = this;

            t.selection.allowResize(false);

            t.getElement().animate({
                    top: y + "px",
                    left: x + "px",
                    width: w + "px",
                    height: h + "px"
                },
                {
                    easing: t.core.opt.animEasing,
                    duration: t.core.opt.animDuration,
                    complete: function() {
                        t.selection.allowResize(true);
                        cb && cb.call(this);
                    },
                    progress: function(anim) {
                        var props = {}, i, tw = anim.tweens;

                        for (i = 0; i < tw.length; i++) {
                            props[tw[i].prop] = tw[i].now;
                        }

                        var b = {
                            x: parseInt(props.left),
                            y: parseInt(props.top),
                            w: parseInt(props.width),
                            h: parseInt(props.height)
                        };

                        b.x2 = b.x + b.w;
                        b.y2 = b.y + b.h;

                        t.selection.updateRaw(b, "se");
                    }
                });
        }

    };
    Jcrop.registerComponent("Animator", CropAnimator);


    /**
     *  DragState
     *  an object that handles dragging events
     *
     *  This object is used by the built-in selection object to
     *  track a dragging operation on a selection
     */
    // var DragState = function(e,selection,ord){{{
    var DragState = function(e, selection, ord) {
        var t = this;

        t.x = e.pageX;
        t.y = e.pageY;

        t.selection = selection;
        t.eventTarget = selection.core.opt.dragEventTarget;
        t.orig = selection.get();

        selection.callFilterFunction("refresh");

        var p = selection.core.container.position();
        t.elx = p.left;
        t.ely = p.top;

        t.offsetx = 0;
        t.offsety = 0;
        t.ord = ord;
        t.opposite = t.getOppositeCornerOffset();

        t.initEvents(e);

    };
    // }}}

    DragState.prototype = {
        // getOppositeCornerOffset: function(){{{
        // Calculate relative offset of locked corner
        getOppositeCornerOffset: function() {

            var o = this.orig;
            var relx = this.x - this.elx - o.x;
            var rely = this.y - this.ely - o.y;

            switch (this.ord) {
            case "nw":
            case "w":
                return [o.w - relx, o.h - rely];
                return [o.x + o.w, o.y + o.h];

            case "sw":
                return [o.w - relx, -rely];
                return [o.x + o.w, o.y];

            case "se":
            case "s":
            case "e":
                return [-relx, -rely];
                return [o.x, o.y];

            case "ne":
            case "n":
                return [-relx, o.h - rely];
                return [o.w, o.y + o.h];
            }

            return [null, null];
        },
        // }}}
        // initEvents: function(e){{{
        initEvents: function(e) {
            $(this.eventTarget)
                .on("mousemove.jcrop", this.createDragHandler())
                .on("mouseup.jcrop", this.createStopHandler());
        },
        // }}}
        // dragEvent: function(e){{{
        dragEvent: function(e) {
            this.offsetx = e.pageX - this.x;
            this.offsety = e.pageY - this.y;
            this.selection.updateRaw(this.getBox(), this.ord);
        },
        // }}}
        // endDragEvent: function(e){{{
        endDragEvent: function(e) {
            var sel = this.selection;
            sel.core.container.removeClass("jcrop-dragging");
            sel.element.trigger("cropend", [sel, sel.core.unscale(sel.get())]);
            sel.focus();
        },
        // }}}
        // createStopHandler: function(){{{
        createStopHandler: function() {
            var t = this;
            return function(e) {
                $(t.eventTarget).off(".jcrop");
                t.endDragEvent(e);
                return false;
            };
        },
        // }}}
        // createDragHandler: function(){{{
        createDragHandler: function() {
            var t = this;
            return function(e) {
                t.dragEvent(e);
                return false;
            };
        },
        // }}}
        //update: function(x,y){{{
        update: function(x, y) {
            var t = this;
            t.offsetx = x - t.x;
            t.offsety = y - t.y;
        },
        //}}}
        //resultWrap: function(d){{{
        resultWrap: function(d) {
            var b = {
                x: Math.min(d[0], d[2]),
                y: Math.min(d[1], d[3]),
                x2: Math.max(d[0], d[2]),
                y2: Math.max(d[1], d[3])
            };

            b.w = b.x2 - b.x;
            b.h = b.y2 - b.y;

            return b;
        },
        //}}}
        //getBox: function(){{{
        getBox: function() {
            var t = this;
            var o = t.orig;
            var _c = { x2: o.x + o.w, y2: o.y + o.h };
            switch (t.ord) {
            case "n":
                return t.resultWrap([o.x, t.offsety + o.y, _c.x2, _c.y2]);
            case "s":
                return t.resultWrap([o.x, o.y, _c.x2, t.offsety + _c.y2]);
            case "e":
                return t.resultWrap([o.x, o.y, t.offsetx + _c.x2, _c.y2]);
            case "w":
                return t.resultWrap([o.x + t.offsetx, o.y, _c.x2, _c.y2]);
            case "sw":
                return t.resultWrap([t.offsetx + o.x, o.y, _c.x2, t.offsety + _c.y2]);
            case "se":
                return t.resultWrap([o.x, o.y, t.offsetx + _c.x2, t.offsety + _c.y2]);
            case "ne":
                return t.resultWrap([o.x, t.offsety + o.y, t.offsetx + _c.x2, _c.y2]);
            case "nw":
                return t.resultWrap([t.offsetx + o.x, t.offsety + o.y, _c.x2, _c.y2]);
            case "move":
                _c.nx = o.x + t.offsetx;
                _c.ny = o.y + t.offsety;
                return t.resultWrap([_c.nx, _c.ny, _c.nx + o.w, _c.ny + o.h]);
            }
        }
        //}}}
    };
    Jcrop.registerComponent("DragState", DragState);


    /**
     *  EventManager
     *  provides internal event support
     */
    var EventManager = function(core) {
        this.core = core;
    };
    EventManager.prototype = {
        on: function(n, cb) { $(this).on(n, cb); },
        off: function(n) { $(this).off(n); },
        trigger: function(n) { $(this).trigger(n); }
    };
    Jcrop.registerComponent("EventManager", EventManager);


    /**
     * Image Loader
     * Reliably pre-loads images
     */
    // var ImageLoader = function(src,element,cb){{{
    var ImageLoader = function(src, element, cb) {
        this.src = src;
        if (!element) element = new Image;
        this.element = element;
        this.callback = cb;
        this.load();
    };
    // }}}

    $.extend(ImageLoader,
        {
            // attach: function(el,cb){{{
            attach: function(el, cb) {
                return new ImageLoader(el.src, el, cb);
            },
            // }}}
            // prototype: {{{
            prototype: {
                getDimensions: function() {
                    var el = this.element;

                    if (el.naturalWidth)
                        return [el.naturalWidth, el.naturalHeight];

                    if (el.width)
                        return [el.width, el.height];

                    return null;
                },
                fireCallback: function() {
                    this.element.onload = null;
                    if (typeof this.callback == "function")
                        this.callback.apply(this, this.getDimensions());
                },
                isLoaded: function() {
                    return this.element.complete;
                },
                load: function() {
                    var t = this;
                    var el = t.element;

                    el.src = t.src;

                    if (t.isLoaded()) t.fireCallback();
                    else
                        t.element.onload = function(e) {
                            t.fireCallback();
                        };
                }
            }
            // }}}
        });
    Jcrop.registerComponent("ImageLoader", ImageLoader);


    /**
     * JcropTouch
     * Detects and enables mobile touch support
     */
    // var JcropTouch = function(core){{{
    var JcropTouch = function(core) {
        this.core = core;
        this.init();
    };
    // }}}

    $.extend(JcropTouch,
        {
            // support: function(){{{
            support: function() {
                if (("ontouchstart" in window) || window.DocumentTouch && document instanceof DocumentTouch)
                    return true;
            },
            // }}}
            prototype: {
                // init: function(){{{
                init: function() {
                    var t = this,
                        p = $.Jcrop.component.DragState.prototype;

                    // A bit of an ugly hack to make sure we modify prototype
                    // only once, store a key on the prototype
                    if (!p.touch) {
                        t.initEvents();
                        t.shimDragState();
                        t.shimStageDrag();
                        p.touch = true;
                    }
                },
                // }}}
                // shimDragState: function(){{{
                shimDragState: function() {
                    var t = this;
                    $.Jcrop.component.DragState.prototype.initEvents = function(e) {

                        // Attach subsequent drag event handlers based on initial
                        // event type - avoids collecting "pseudo-mouse" events
                        // generated by some mobile browsers in some circumstances
                        if (e.type.substr(0, 5) == "touch") {

                            $(this.eventTarget)
                                .on("touchmove.jcrop.jcrop-touch", t.dragWrap(this.createDragHandler()))
                                .on("touchend.jcrop.jcrop-touch", this.createStopHandler());

                        }
                        // For other events, use the mouse handlers that
                        // the default DragState.initEvents() method sets...
                        else {

                            $(this.eventTarget)
                                .on("mousemove.jcrop", this.createDragHandler())
                                .on("mouseup.jcrop", this.createStopHandler());

                        }

                    };
                },
                // }}}
                // shimStageDrag: function(){{{
                shimStageDrag: function() {
                    this.core.container
                        .addClass("jcrop-touch")
                        .on("touchstart.jcrop.jcrop-stage", this.dragWrap(this.core.ui.manager.startDragHandler()));
                },
                // }}}
                // dragWrap: function(cb){{{
                dragWrap: function(cb) {
                    return function(e) {
                        e.preventDefault();
                        e.stopPropagation();
                        if (e.type.substr(0, 5) == "touch") {
                            e.pageX = e.originalEvent.changedTouches[0].pageX;
                            e.pageY = e.originalEvent.changedTouches[0].pageY;
                            return cb(e);
                        }
                        return false;
                    };
                },
                // }}}
                // initEvents: function(){{{
                initEvents: function() {
                    var t = this, c = t.core;

                    c.container.on(
                        "touchstart.jcrop.jcrop-touch",
                        "." + c.opt.css_drag,
                        t.dragWrap(c.startDrag())
                    );
                }
                // }}}
            }
        });
    Jcrop.registerComponent("Touch", JcropTouch);


    /**
     *  KeyWatcher
     *  provides keyboard support
     */
    // var KeyWatcher = function(core){{{
    var KeyWatcher = function(core) {
        this.core = core;
        this.init();
    };
    // }}}
    $.extend(KeyWatcher,
        {
            // defaults: {{{
            defaults: {
                eventName: "keydown.jcrop",
                passthru: [9],
                debug: false
            },
            // }}}
            prototype: {
                // init: function(){{{
                init: function() {
                    $.extend(this, KeyWatcher.defaults);
                    this.enable();
                },
                // }}}
                // disable: function(){{{
                disable: function() {
                    this.core.container.off(this.eventName);
                },
                // }}}
                // enable: function(){{{
                enable: function() {
                    var t = this, m = t.core;
                    m.container.on(t.eventName,
                        function(e) {
                            var nudge = e.shiftKey ? 16 : 2;

                            if ($.inArray(e.keyCode, t.passthru) >= 0)
                                return true;

                            switch (e.keyCode) {
                            case 37:
                                m.nudge(-nudge, 0);
                                break;
                            case 38:
                                m.nudge(0, -nudge);
                                break;
                            case 39:
                                m.nudge(nudge, 0);
                                break;
                            case 40:
                                m.nudge(0, nudge);
                                break;

                            case 46:
                            case 8:
                                m.requestDelete();
                                return false;
                                break;

                            default:
                                if (t.debug) console.log("keycode: " + e.keyCode);
                                break;
                            }

                            if (!e.metaKey && !e.ctrlKey)
                                e.preventDefault();
                        });
                }
                // }}}
            }
        });
    Jcrop.registerComponent("Keyboard", KeyWatcher);


    /**
     * Selection
     * Built-in selection object
     */
    var Selection = function() {};

    $.extend(Selection,
        {
            // defaults: {{{
            defaults: {
                minSize: [8, 8],
                maxSize: [0, 0],
                aspectRatio: 0,
                edge: { n: 0, s: 0, e: 0, w: 0 },
                bgColor: null,
                bgOpacity: null,
                last: null,

                state: null,
                active: true,
                linked: true,
                canDelete: true,
                canDrag: true,
                canResize: true,
                canSelect: true
            },
            // }}}
            prototype: {
                // init: function(core){{{
                init: function(core) {
                    this.core = core;
                    this.startup();
                    this.linked = this.core.opt.linked;
                    this.attach();
                    this.setOptions(this.core.opt);
                    core.container.trigger("cropcreate", [this]);
                },
                // }}}
                // attach: function(){{{
                attach: function() {
                    // For extending init() sequence
                },
                // }}}
                // startup: function(){{{
                startup: function() {
                    var t = this, o = t.core.opt;
                    $.extend(t, Selection.defaults);
                    t.filter = t.core.getDefaultFilters();

                    t.element = $("<div />").addClass(o.css_selection).data({ selection: t });
                    t.frame = $("<button />").addClass(o.css_button).data("ord", "move").attr("type", "button");
                    t.element.append(t.frame).appendTo(t.core.container);

                    // IE background/draggable hack
                    if (t.core.opt.is_msie)
                        t.frame.css({
                            opacity: 0,
                            backgroundColor: "white"
                        });

                    t.insertElements();

                    // Bind focus and blur events for this selection
                    t.frame.on("focus.jcrop",
                        function(e) {
                            t.core.setSelection(t);
                            t.element.trigger("cropfocus", t);
                            t.element.addClass("jcrop-focus");
                        }).on("blur.jcrop",
                        function(e) {
                            t.element.removeClass("jcrop-focus");
                            t.element.trigger("cropblur", t);
                        });
                },
                // }}}
                // propagate: [{{{
                propagate: [
                    "canDelete", "canDrag", "canResize", "canSelect",
                    "minSize", "maxSize", "aspectRatio", "edge"
                ],
                // }}}
                // setOptions: function(opt){{{
                setOptions: function(opt) {
                    Jcrop.propagate(this.propagate, opt, this);
                    this.refresh();
                    return this;
                },
                // }}}
                // refresh: function(){{{
                refresh: function() {
                    this.allowResize();
                    this.allowDrag();
                    this.allowSelect();
                    this.callFilterFunction("refresh");
                    this.updateRaw(this.get(), "se");
                },
                // }}}
                // callFilterFunction: function(f,args){{{
                callFilterFunction: function(f, args) {
                    for (var i = 0; i < this.filter.length; i++)
                        if (this.filter[i][f]) this.filter[i][f](this);
                    return this;
                },
                // }}}
                //addFilter: function(filter){{{
                addFilter: function(filter) {
                    filter.core = this.core;
                    if (!this.hasFilter(filter)) {
                        this.filter.push(filter);
                        this.sortFilters();
                        if (filter.init) filter.init();
                        this.refresh();
                    }
                },
                //}}}
                // hasFilter: function(filter){{{
                hasFilter: function(filter) {
                    var i, f = this.filter, n = [];
                    for (i = 0; i < f.length; i++) if (f[i] === filter) return true;
                },
                // }}}
                // sortFilters: function(){{{
                sortFilters: function() {
                    this.filter.sort(
                        function(x, y) { return x.priority - y.priority; }
                    );
                },
                // }}}
                //clearFilters: function(){{{
                clearFilters: function() {
                    var i, f = this.filter;

                    for (var i = 0; i < f.length; i++)
                        if (f[i].destroy) f[i].destroy();

                    this.filter = [];
                },
                //}}}
                // removeFiltersByTag: function(tag){{{
                removeFilter: function(tag) {
                    var i, f = this.filter, n = [];

                    for (var i = 0; i < f.length; i++)
                        if ((f[i].tag && (f[i].tag == tag)) || (tag === f[i])) {
                            if (f[i].destroy) f[i].destroy();
                        } else n.push(f[i]);

                    this.filter = n;
                },
                // }}}
                // runFilters: function(b,ord){{{
                runFilters: function(b, ord) {
                    for (var i = 0; i < this.filter.length; i++)
                        b = this.filter[i].filter(b, ord, this);
                    return b;
                },
                // }}}
                //endDrag: function(){{{
                endDrag: function() {
                    if (this.state) {
                        $(document.body).off(".jcrop");
                        this.focus();
                        this.state = null;
                    }
                },
                //}}}
                // startDrag: function(e,ord){{{
                startDrag: function(e, ord) {
                    var t = this;
                    var m = t.core;

                    ord = ord || $(e.target).data("ord");

                    this.focus();

                    if ((ord == "move") && t.element.hasClass(t.core.opt.css_nodrag))
                        return false;

                    this.state = new Jcrop.component.DragState(e, this, ord);
                    return false;
                },
                // }}}
                // allowSelect: function(v){{{
                allowSelect: function(v) {
                    if (v === undefined) v = this.canSelect;

                    if (v && this.canSelect) this.frame.attr("disabled", false);
                    else this.frame.attr("disabled", "disabled");

                    return this;
                },
                // }}}
                // allowDrag: function(v){{{
                allowDrag: function(v) {
                    var t = this, o = t.core.opt;
                    if (v == undefined) v = t.canDrag;

                    if (v && t.canDrag) t.element.removeClass(o.css_nodrag);
                    else t.element.addClass(o.css_nodrag);

                    return this;
                },
                // }}}
                // allowResize: function(v){{{
                allowResize: function(v) {
                    var t = this, o = t.core.opt;
                    if (v == undefined) v = t.canResize;

                    if (v && t.canResize) t.element.removeClass(o.css_noresize);
                    else t.element.addClass(o.css_noresize);

                    return this;
                },
                // }}}
                // remove: function(){{{
                remove: function() {
                    this.element.trigger("cropremove", this);
                    this.element.remove();
                },
                // }}}
                // toBack: function(){{{
                toBack: function() {
                    this.active = false;
                    this.element.removeClass("jcrop-current jcrop-focus");
                },
                // }}}
                // toFront: function(){{{
                toFront: function() {
                    this.active = true;
                    this.element.addClass("jcrop-current");
                    this.callFilterFunction("refresh");
                    this.refresh();
                },
                // }}}
                // redraw: function(b){{{
                redraw: function(b) {
                    this.moveTo(b.x, b.y);
                    this.resize(b.w, b.h);
                    this.last = b;
                    return this;
                },
                // }}}
                // update: function(b,ord){{{
                update: function(b, ord) {
                    return this.updateRaw(this.core.scale(b), ord);
                },
                // }}}
                // update: function(b,ord){{{
                updateRaw: function(b, ord) {
                    b = this.runFilters(b, ord);
                    this.redraw(b);
                    this.element.trigger("cropmove", [this, this.core.unscale(b)]);
                    return this;
                },
                // }}}
                // animateTo: function(box,cb){{{
                animateTo: function(box, cb) {
                    var ca = new Jcrop.component.Animator(this),
                        b = this.core.scale(Jcrop.wrapFromXywh(box));

                    ca.animate(b.x, b.y, b.w, b.h, cb);
                },
                // }}}
                // center: function(instant){{{
                center: function(instant) {
                    var b = this.get(), m = this.core;
                    var elw = m.container.width(), elh = m.container.height();
                    var box = [(elw - b.w) / 2, (elh - b.h) / 2, b.w, b.h];
                    return this[instant ? "setSelect" : "animateTo"](box);
                },
                // }}}
                //createElement: function(type,ord){{{
                createElement: function(type, ord) {
                    return $("<div />").addClass(type + " ord-" + ord).data("ord", ord);
                },
                //}}}
                //moveTo: function(x,y){{{
                moveTo: function(x, y) {
                    this.element.css({ top: y + "px", left: x + "px" });
                },
                //}}}
                // blur: function(){{{
                blur: function() {
                    this.element.blur();
                    return this;
                },
                // }}}
                // focus: function(){{{
                focus: function() {
                    this.core.setSelection(this);
                    this.frame.focus();
                    return this;
                },
                // }}}
                //resize: function(w,h){{{
                resize: function(w, h) {
                    this.element.css({ width: w + "px", height: h + "px" });
                },
                //}}}
                //get: function(){{{
                get: function() {
                    var b = this.element,
                        o = b.position(),
                        w = b.width(),
                        h = b.height(),
                        rv = { x: o.left, y: o.top };

                    rv.x2 = rv.x + w;
                    rv.y2 = rv.y + h;
                    rv.w = w;
                    rv.h = h;

                    return rv;
                },
                //}}}
                //insertElements: function(){{{
                insertElements: function() {
                    var t = this,
                        i,
                        m = t.core,
                        fr = t.element,
                        o = t.core.opt,
                        b = o.borders,
                        h = o.handles,
                        d = o.dragbars;

                    for (i = 0; i < d.length; i++)
                        fr.append(t.createElement(o.css_dragbars, d[i]));

                    for (i = 0; i < h.length; i++)
                        fr.append(t.createElement(o.css_handles, h[i]));

                    for (i = 0; i < b.length; i++)
                        fr.append(t.createElement(o.css_borders, b[i]));
                }
                //}}}
            }
        });
    Jcrop.registerComponent("Selection", Selection);


    /**
     * StageDrag
     * Facilitates dragging
     */
    // var StageDrag = function(manager,opt){{{
    var StageDrag = function(manager, opt) {
        $.extend(this, StageDrag.defaults, opt || {});
        this.manager = manager;
        this.core = manager.core;
    };
    // }}}
    // StageDrag.defaults = {{{
    StageDrag.defaults = {
        offset: [-8, -8],
        active: true,
        minsize: [20, 20]
    };
    // }}}

    $.extend(StageDrag.prototype,
        {
            // start: function(e){{{
            start: function(e) {
                var c = this.core;

                // Do nothing if allowSelect is off
                if (!c.opt.allowSelect) return;

                // Also do nothing if we can't draw any more selections
                if (c.opt.multi && c.opt.multiMax && (c.ui.multi.length >= c.opt.multiMax)) return false;

                // calculate a few variables for this drag operation
                var o = $(e.currentTarget).offset();
                var origx = e.pageX - o.left + this.offset[0];
                var origy = e.pageY - o.top + this.offset[1];
                var m = c.ui.multi;

                // Determine newly dragged crop behavior if multi disabled
                if (!c.opt.multi) {
                    // For multiCleaanup true, remove all existing selections
                    if (c.opt.multiCleanup) {
                        for (var i = 0; i < m.length; i++) m[i].remove();
                        c.ui.multi = [];
                    }
                    // If not, only remove the currently active selection
                    else {
                        c.removeSelection(c.ui.selection);
                    }
                }

                c.container.addClass("jcrop-dragging");

                // Create the new selection
                var sel = c.newSelection()
                    // and position it
                    .updateRaw(Jcrop.wrapFromXywh([origx, origy, 1, 1]));

                sel.element.trigger("cropstart", [sel, this.core.unscale(sel.get())]);

                return sel.startDrag(e, "se");
            },
            // }}}
            // end: function(x,y){{{
            end: function(x, y) {
                this.drag(x, y);
                var b = this.sel.get();

                this.core.container.removeClass("jcrop-dragging");

                if ((b.w < this.minsize[0]) || (b.h < this.minsize[1]))
                    this.core.requestDelete();
                else this.sel.focus();
            }
            // }}}
        });
    Jcrop.registerComponent("StageDrag", StageDrag);


    /**
     * StageManager
     * Provides basic stage-specific functionality
     */
    // var StageManager = function(core){{{
    var StageManager = function(core) {
        this.core = core;
        this.ui = core.ui;
        this.init();
    };
    // }}}

    $.extend(StageManager.prototype,
        {
            // init: function(){{{
            init: function() {
                this.setupEvents();
                this.dragger = new StageDrag(this);
            },
            // }}}
            // tellConfigUpdate: function(options){{{
            tellConfigUpdate: function(options) {
                for (var i = 0, m = this.ui.multi, l = m.length; i < l; i++)
                    if (m[i].setOptions && (m[i].linked || (this.core.opt.linkCurrent && m[i] == this.ui.selection)))
                        m[i].setOptions(options);
            },
            // }}}
            // startDragHandler: function(){{{
            startDragHandler: function() {
                var t = this;
                return function(e) {
                    if (!e.button || t.core.opt.is_ie_lt9) return t.dragger.start(e);
                };
            },
            // }}}
            // removeEvents: function(){{{
            removeEvents: function() {
                this.core.event.off(".jcrop-stage");
                this.core.container.off(".jcrop-stage");
            },
            // }}}
            // shimLegacyHandlers: function(options){{{
            // This method uses the legacyHandlers configuration object to
            // gracefully wrap old-style Jcrop events with new ones
            shimLegacyHandlers: function(options) {
                var _x = {}, core = this.core, tmp;

                $.each(core.opt.legacyHandlers,
                    function(k, i) {
                        if (k in options) {
                            tmp = options[k];
                            core.container.off(".jcrop-" + k)
                                .on(i + ".jcrop.jcrop-" + k,
                                    function(e, s, c) {
                                        tmp.call(core, c);
                                    });
                            delete options[k];
                        }
                    });
            },
            // }}}
            // setupEvents: function(){{{
            setupEvents: function() {
                var t = this, c = t.core;

                c.event.on("configupdate.jcrop-stage",
                    function(e) {
                        t.shimLegacyHandlers(c.opt);
                        t.tellConfigUpdate(c.opt);
                        c.container.trigger("cropconfig", [c, c.opt]);
                    });

                this.core.container
                    .on("mousedown.jcrop.jcrop-stage", this.startDragHandler());
            }
            // }}}
        });
    Jcrop.registerComponent("StageManager", StageManager);


    var Thumbnailer = function() {
    };

    $.extend(Thumbnailer,
        {
            defaults: {
                // Set to a specific Selection object
                // If this value is set, the preview will only track that Selection
                selection: null,

                fading: true,
                fadeDelay: 1000,
                fadeDuration: 1000,
                autoHide: false,
                width: 80,
                height: 80,
                _hiding: null
            },

            prototype: {
                recopyCanvas: function() {
                    var s = this.core.ui.stage, cxt = s.context;
                    this.context.putImageData(cxt.getImageData(0, 0, s.canvas.width, s.canvas.height), 0, 0);
                },
                init: function(core, options) {
                    var t = this;
                    this.core = core;
                    $.extend(this, Thumbnailer.defaults, options);
                    t.initEvents();
                    t.refresh();
                    t.insertElements();
                    if (t.selection) {
                        t.renderSelection(t.selection);
                        t.selectionTarget = t.selection.element[0];
                    } else if (t.core.ui.selection) {
                        t.renderSelection(t.core.ui.selection);
                    }

                    if (t.core.ui.stage.canvas) {
                        t.context = t.preview[0].getContext("2d");
                        t.core.container.on("cropredraw",
                            function(e) {
                                t.recopyCanvas();
                                t.refresh();
                            });
                    }
                },
                updateImage: function(imgel) {
                    this.preview.remove();
                    this.preview = $($.Jcrop.imageClone(imgel));
                    this.element.append(this.preview);
                    this.refresh();
                    return this;
                },
                insertElements: function() {
                    this.preview = $($.Jcrop.imageClone(this.core.ui.stage.imgsrc));

                    this.element = $("<div />").addClass("jcrop-thumb")
                        .width(this.width).height(this.height)
                        .append(this.preview)
                        .appendTo(this.core.container);
                },
                resize: function(w, h) {
                    this.width = w;
                    this.height = h;
                    this.element.width(w).height(h);
                    this.renderCoords(this.last);
                },
                refresh: function() {
                    this.cw = (this.core.opt.xscale * this.core.container.width());
                    this.ch = (this.core.opt.yscale * this.core.container.height());
                    if (this.last) {
                        this.renderCoords(this.last);
                    }
                },
                renderCoords: function(c) {
                    var rx = this.width / c.w;
                    var ry = this.height / c.h;

                    this.preview.css({
                        width: Math.round(rx * this.cw) + "px",
                        height: Math.round(ry * this.ch) + "px",
                        marginLeft: "-" + Math.round(rx * c.x) + "px",
                        marginTop: "-" + Math.round(ry * c.y) + "px"
                    });

                    this.last = c;
                    return this;
                },
                renderSelection: function(s) {
                    return this.renderCoords(s.core.unscale(s.get()));
                },
                selectionStart: function(s) {
                    this.renderSelection(s);
                },
                show: function() {
                    if (this._hiding) clearTimeout(this._hiding);

                    if (!this.fading) this.element.stop().css({ opacity: 1 });
                    else this.element.stop().animate({ opacity: 1 }, { duration: 80, queue: false });
                },
                hide: function() {
                    var t = this;
                    if (!t.fading) t.element.hide();
                    else
                        t._hiding = setTimeout(function() {
                                t._hiding = null;
                                t.element.stop().animate({ opacity: 0 }, { duration: t.fadeDuration, queue: false });
                            },
                            t.fadeDelay);
                },
                initEvents: function() {
                    var t = this;
                    t.core.container.on("croprotstart croprotend cropimage cropstart cropmove cropend",
                        function(e, s, c) {
                            if (t.selectionTarget && (t.selectionTarget !== e.target)) return false;

                            switch (e.type) {
                            case "cropimage":
                                t.updateImage(c);
                                break;

                            case "cropstart":
                                t.selectionStart(s);
                            case "croprotstart":
                                t.show();
                                break;

                            case "cropend":
                                t.renderCoords(c);
                            case "croprotend":
                                if (t.autoHide) t.hide();
                                break;

                            case "cropmove":
                                t.renderCoords(c);
                                break;
                            }
                        });
                }
            }
        });
    Jcrop.registerComponent("Thumbnailer", Thumbnailer);


    /**
     * DialDrag component
     * This is a little hacky, it was adapted from some previous/old code
     * Plan to update this API in the future
     */
    var DialDrag = function() {};

    DialDrag.prototype = {
        init: function(core, actuator, callback) {
            var that = this;

            if (!actuator) actuator = core.container;
            this.$btn = $(actuator);
            this.$targ = $(actuator);
            this.core = core;

            this.$btn
                .addClass("dialdrag")
                .on("mousedown.dialdrag", this.mousedown())
                .data("dialdrag", this);

            if (!$.isFunction(callback)) callback = function() {};
            this.callback = callback;
            this.ondone = callback;
        },

        remove: function() {
            this.$btn
                .removeClass("dialdrag")
                .off(".dialdrag")
                .data("dialdrag", null);
            return this;
        },

        setTarget: function(obj) {
            this.$targ = $(obj);
            return this;
        },

        getOffset: function() {
            var targ = this.$targ, pos = targ.offset();
            return [
                pos.left + (targ.width() / 2),
                pos.top + (targ.height() / 2)
            ];
        },

        relMouse: function(e) {
            var x = e.pageX - this.offset[0],
                y = e.pageY - this.offset[1],
                ang = Math.atan2(y, x) * (180 / Math.PI),
                vec = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
            return [x, y, ang, vec];
        },

        mousedown: function() {
            var that = this;

            function mouseUp(e) {
                $(window).off(".dialdrag");
                that.ondone.call(that, that.relMouse(e));
                that.core.container.trigger("croprotend");
            }

            function mouseMove(e) {
                that.callback.call(that, that.relMouse(e));
            }

            return function(e) {
                that.offset = that.getOffset();
                var rel = that.relMouse(e);
                that.angleOffset = -that.core.ui.stage.angle + rel[2];
                that.distOffset = rel[3];
                that.dragOffset = [rel[0], rel[1]];
                that.core.container.trigger("croprotstart");

                $(window)
                    .on("mousemove.dialdrag", mouseMove)
                    .on("mouseup.dialdrag", mouseUp);

                that.callback.call(that, that.relMouse(e));

                return false;
            };
        }

    };
    Jcrop.registerComponent("DialDrag", DialDrag);


    /////////////////////////////////
    // DEFAULT SETTINGS

    Jcrop.defaults = {

        // Selection Behavior
        edge: { n: 0, s: 0, e: 0, w: 0 },
        setSelect: null,
        linked: true,
        linkCurrent: true,
        canDelete: true,
        canSelect: true,
        canDrag: true,
        canResize: true,

        // Component constructors
        eventManagerComponent: Jcrop.component.EventManager,
        keyboardComponent: Jcrop.component.Keyboard,
        dragstateComponent: Jcrop.component.DragState,
        stagemanagerComponent: Jcrop.component.StageManager,
        animatorComponent: Jcrop.component.Animator,
        selectionComponent: Jcrop.component.Selection,

        // This is a function that is called, which returns a stage object
        stageConstructor: Jcrop.stageConstructor,

        // Stage Behavior
        allowSelect: true,
        multi: false,
        multiMax: false,
        multiCleanup: true,
        animation: true,
        animEasing: "swing",
        animDuration: 400,
        fading: true,
        fadeDuration: 300,
        fadeEasing: "swing",
        bgColor: "black",
        bgOpacity: .5,

        // Startup options
        applyFilters: ["constrain", "extent", "backoff", "ratio", "shader", "round"],
        borders: ["e", "w", "s", "n"],
        handles: ["n", "s", "e", "w", "sw", "ne", "nw", "se"],
        dragbars: ["n", "e", "w", "s"],

        dragEventTarget: window,

        xscale: 1,
        yscale: 1,

        boxWidth: null,
        boxHeight: null,

        // CSS Classes
        // @todo: These need to be moved to top-level object keys
        // for better customization. Currently if you try to extend one
        // via an options object to Jcrop, it will wipe out all
        // the others you don't specify. Be careful for now!
        css_nodrag: "jcrop-nodrag",
        css_drag: "jcrop-drag",
        css_container: "jcrop-active",
        css_shades: "jcrop-shades",
        css_selection: "jcrop-selection",
        css_borders: "jcrop-border",
        css_handles: "jcrop-handle jcrop-drag",
        css_button: "jcrop-box jcrop-drag",
        css_noresize: "jcrop-noresize",
        css_dragbars: "jcrop-dragbar jcrop-drag",

        legacyHandlers: {
            onChange: "cropmove",
            onSelect: "cropend"
        }

    };


    // Jcrop API methods
    $.extend(Jcrop.prototype,
        {
            //init: function(){{{
            init: function() {
                this.event = new this.opt.eventManagerComponent(this);
                this.ui.keyboard = new this.opt.keyboardComponent(this);
                this.ui.manager = new this.opt.stagemanagerComponent(this);
                this.applyFilters();

                if ($.Jcrop.supportsTouch)
                    new $.Jcrop.component.Touch(this);

                this.initEvents();
            },
            //}}}
            // applySizeConstraints: function(){{{
            applySizeConstraints: function() {
                var o = this.opt,
                    img = this.opt.imgsrc;

                if (img) {

                    var iw = img.naturalWidth || img.width,
                        ih = img.naturalHeight || img.height,
                        bw = o.boxWidth || iw,
                        bh = o.boxHeight || ih;

                    if (img && ((iw > bw) || (ih > bh))) {
                        var bx = Jcrop.getLargestBox(iw / ih, bw, bh);
                        $(img).width(bx[0]).height(bx[1]);
                        this.resizeContainer(bx[0], bx[1]);
                        this.opt.xscale = iw / bx[0];
                        this.opt.yscale = ih / bx[1];
                    }

                }

                if (this.opt.trueSize) {
                    var dw = this.opt.trueSize[0];
                    var dh = this.opt.trueSize[1];
                    var cs = this.getContainerSize();
                    this.opt.xscale = dw / cs[0];
                    this.opt.yscale = dh / cs[1];
                }
            },
            // }}}
            initComponent: function(name) {
                if (Jcrop.component[name]) {
                    var args = Array.prototype.slice.call(arguments);
                    var obj = new Jcrop.component[name];
                    args.shift();
                    args.unshift(this);
                    obj.init.apply(obj, args);
                    return obj;
                }
            },
            // setOptions: function(opt){{{
            setOptions: function(opt, proptype) {

                if (!$.isPlainObject(opt)) opt = {};

                $.extend(this.opt, opt);

                // Handle a setSelect value
                if (this.opt.setSelect) {

                    // If there is no current selection
                    // passing setSelect will create one
                    if (!this.ui.multi.length)
                        this.newSelection();

                    // Use these values to update the current selection
                    this.setSelect(this.opt.setSelect);

                    // Set to null so it doesn't get called again
                    this.opt.setSelect = null;
                }

                this.event.trigger("configupdate");
                return this;
            },
            // }}}
            //destroy: function(){{{
            destroy: function() {
                if (this.opt.imgsrc) {
                    this.container.before(this.opt.imgsrc);
                    this.container.remove();
                    $(this.opt.imgsrc).removeData("Jcrop").show();
                } else {
                    // @todo: more elegant destroy() process for non-image containers
                    this.container.remove();
                }
            },
            // }}}
            // applyFilters: function(){{{
            applyFilters: function() {
                var obj;
                for (var i = 0, f = this.opt.applyFilters, l = f.length; i < l; i++) {
                    if ($.Jcrop.filter[f[i]])
                        obj = new $.Jcrop.filter[f[i]];
                    obj.core = this;
                    if (obj.init) obj.init();
                    this.filter[f[i]] = obj;
                }
            },
            // }}}
            // getDefaultFilters: function(){{{
            getDefaultFilters: function() {
                var rv = [];

                for (var i = 0, f = this.opt.applyFilters, l = f.length; i < l; i++)
                    if (this.filter.hasOwnProperty(f[i]))
                        rv.push(this.filter[f[i]]);

                rv.sort(function(x, y) { return x.priority - y.priority; });
                return rv;
            },
            // }}}
            // setSelection: function(sel){{{
            setSelection: function(sel) {
                var m = this.ui.multi;
                var n = [];
                for (var i = 0; i < m.length; i++) {
                    if (m[i] !== sel) n.push(m[i]);
                    m[i].toBack();
                }
                n.unshift(sel);
                this.ui.multi = n;
                this.ui.selection = sel;
                sel.toFront();
                return sel;
            },
            // }}}
            // getSelection: function(raw){{{
            getSelection: function(raw) {
                var b = this.ui.selection.get();
                return b;
            },
            // }}}
            // newSelection: function(){{{
            newSelection: function(sel) {
                if (!sel)
                    sel = new this.opt.selectionComponent();

                sel.init(this);
                this.setSelection(sel);

                return sel;
            },
            // }}}
            // hasSelection: function(sel){{{
            hasSelection: function(sel) {
                for (var i = 0; i < this.ui.multi; i++)
                    if (sel === this.ui.multi[i]) return true;
            },
            // }}}
            // removeSelection: function(sel){{{
            removeSelection: function(sel) {
                var i, n = [], m = this.ui.multi;
                for (var i = 0; i < m.length; i++) {
                    if (sel !== m[i])
                        n.push(m[i]);
                    else m[i].remove();
                }
                return this.ui.multi = n;
            },
            // }}}
            //addFilter: function(filter){{{
            addFilter: function(filter) {
                for (var i = 0, m = this.ui.multi, l = m.length; i < l; i++)
                    m[i].addFilter(filter);

                return this;
            },
            //}}}
            // removeFiltersByTag: function(tag){{{
            removeFilter: function(filter) {
                for (var i = 0, m = this.ui.multi, l = m.length; i < l; i++)
                    m[i].removeFilter(filter);

                return this;
            },
            // }}}
            // blur: function(){{{
            blur: function() {
                this.ui.selection.blur();
                return this;
            },
            // }}}
            // focus: function(){{{
            focus: function() {
                this.ui.selection.focus();
                return this;
            },
            // }}}
            //initEvents: function(){{{
            initEvents: function() {
                var t = this;
                t.container.on("selectstart", function(e) { return false; })
                    .on("mousedown", "." + t.opt.css_drag, t.startDrag());
            },
            //}}}
            // maxSelect: function(){{{
            maxSelect: function() {
                this.setSelect([0, 0, this.elw, this.elh]);
            },
            // }}}
            // nudge: function(x,y){{{
            nudge: function(x, y) {
                var s = this.ui.selection, b = s.get();

                b.x += x;
                b.x2 += x;
                b.y += y;
                b.y2 += y;

                if (b.x < 0) {
                    b.x2 = b.w;
                    b.x = 0;
                } else if (b.x2 > this.elw) {
                    b.x2 = this.elw;
                    b.x = b.x2 - b.w;
                }

                if (b.y < 0) {
                    b.y2 = b.h;
                    b.y = 0;
                } else if (b.y2 > this.elh) {
                    b.y2 = this.elh;
                    b.y = b.y2 - b.h;
                }

                s.element.trigger("cropstart", [s, this.unscale(b)]);
                s.updateRaw(b, "move");
                s.element.trigger("cropend", [s, this.unscale(b)]);
            },
            // }}}
            // refresh: function(){{{
            refresh: function() {
                for (var i = 0, s = this.ui.multi, l = s.length; i < l; i++)
                    s[i].refresh();
            },
            // }}}
            // blurAll: function(){{{
            blurAll: function() {
                var m = this.ui.multi;
                for (var i = 0; i < m.length; i++) {
                    if (m[i] !== sel) n.push(m[i]);
                    m[i].toBack();
                }
            },
            // }}}
            // scale: function(b){{{
            scale: function(b) {
                var xs = this.opt.xscale,
                    ys = this.opt.yscale;

                return {
                    x: b.x / xs,
                    y: b.y / ys,
                    x2: b.x2 / xs,
                    y2: b.y2 / ys,
                    w: b.w / xs,
                    h: b.h / ys
                };
            },
            // }}}
            // unscale: function(b){{{
            unscale: function(b) {
                var xs = this.opt.xscale,
                    ys = this.opt.yscale;

                return {
                    x: b.x * xs,
                    y: b.y * ys,
                    x2: b.x2 * xs,
                    y2: b.y2 * ys,
                    w: b.w * xs,
                    h: b.h * ys
                };
            },
            // }}}
            // requestDelete: function(){{{
            requestDelete: function() {
                if ((this.ui.multi.length > 1) && (this.ui.selection.canDelete))
                    return this.deleteSelection();
            },
            // }}}
            // deleteSelection: function(){{{
            deleteSelection: function() {
                if (this.ui.selection) {
                    this.removeSelection(this.ui.selection);
                    if (this.ui.multi.length) this.ui.multi[0].focus();
                    this.ui.selection.refresh();
                }
            },
            // }}}
            // animateTo: function(box){{{
            animateTo: function(box) {
                if (this.ui.selection)
                    this.ui.selection.animateTo(box);
                return this;
            },
            // }}}
            // setselect: function(box){{{
            setSelect: function(box) {
                if (this.ui.selection)
                    this.ui.selection.update(Jcrop.wrapFromXywh(box));
                return this;
            },
            // }}}
            //startDrag: function(){{{
            startDrag: function() {
                var t = this;
                return function(e) {
                    var $targ = $(e.target);
                    var selection = $targ.closest("." + t.opt.css_selection).data("selection");
                    var ord = $targ.data("ord");
                    t.container.trigger("cropstart", [selection, t.unscale(selection.get())]);
                    selection.startDrag(e, ord);
                    return false;
                };
            },
            //}}}
            // getContainerSize: function(){{{
            getContainerSize: function() {
                return [this.container.width(), this.container.height()];
            },
            // }}}
            // resizeContainer: function(w,h){{{
            resizeContainer: function(w, h) {
                this.container.width(w).height(h);
                this.refresh();
            },
            // }}}
            // setImage: function(src,cb){{{
            setImage: function(src, cb) {
                var t = this, targ = t.opt.imgsrc;

                if (!targ) return false;

                new $.Jcrop.component.ImageLoader(src,
                    null,
                    function(w, h) {
                        t.resizeContainer(w, h);

                        targ.src = src;
                        $(targ).width(w).height(h);
                        t.applySizeConstraints();
                        t.refresh();
                        t.container.trigger("cropimage", [t, targ]);

                        if (typeof cb == "function")
                            cb.call(t, w, h);
                    });
            },
            // }}}
            // update: function(b){{{
            update: function(b) {
                if (this.ui.selection)
                    this.ui.selection.update(b);
            }
            // }}}

        });

    // Jcrop jQuery plugin function
    $.fn.Jcrop = function(options, callback) {
        options = options || {};

        var first = this.eq(0).data("Jcrop");
        var args = Array.prototype.slice.call(arguments);

        // Return API if requested
        if (options == "api") {
            return first;
        }
        // Allow calling API methods (with arguments)
        else if (first && (typeof options == "string")) {

            // Call method if it exists
            if (first[options]) {
                args.shift();
                first[options].apply(first, args);
                return first;
            }

            // Unknown input/method does not exist
            return false;
        }

        // Otherwise, loop over selected elements
        this.each(function() {
            var t = this, $t = $(this);
            var exists = $t.data("Jcrop");
            var obj;

            // If Jcrop already exists on this element only setOptions()
            if (exists)
                exists.setOptions(options);
            else {

                if (!options.stageConstructor)
                    options.stageConstructor = $.Jcrop.stageConstructor;

                options.stageConstructor(this,
                    options,
                    function(stage, options) {
                        var selection = options.setSelect;
                        if (selection) delete(options.setSelect);

                        var obj = $.Jcrop.attach(stage.element, options);

                        if (typeof stage.attach == "function")
                            stage.attach(obj);

                        $t.data("Jcrop", obj);

                        if (selection) {
                            obj.newSelection();
                            obj.setSelect(selection);
                        }

                        if (typeof callback == "function")
                            callback.call(obj);
                    });
            }

            return this;
        });
    };

/* Modernizr 2.7.1 (Custom Build) | MIT & BSD
 * Build: http://modernizr.com/download/#-csstransforms-canvas-canvastext-draganddrop-inlinesvg-svg-svgclippaths-touch-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-url_data_uri
 */
    ;

    var Modernizr = (function(window, document, undefined) {

        var version = "2.7.1",

            Modernizr = {},


            docElement = document.documentElement,

            mod = "modernizr",
            modElem = document.createElement(mod),
            mStyle = modElem.style,

            inputElem,


            toString = {}.toString,

            prefixes = " -webkit- -moz- -o- -ms- ".split(" "),


            omPrefixes = "Webkit Moz O ms",

            cssomPrefixes = omPrefixes.split(" "),

            domPrefixes = omPrefixes.toLowerCase().split(" "),

            ns = { 'svg': "http://www.w3.org/2000/svg" },

            tests = {},
            inputs = {},
            attrs = {},

            classes = [],

            slice = classes.slice,

            featureName,


            injectElementWithStyles = function(rule, callback, nodes, testnames) {

                var style,
                    ret,
                    node,
                    docOverflow,
                    div = document.createElement("div"),
                    body = document.body,
                    fakeBody = body || document.createElement("body");

                if (parseInt(nodes, 10)) {
                    while (nodes--) {
                        node = document.createElement("div");
                        node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
                        div.appendChild(node);
                    }
                }

                style = ["&#173;", '<style id="s', mod, '">', rule, "</style>"].join("");
                div.id = mod;
                (body ? div : fakeBody).innerHTML += style;
                fakeBody.appendChild(div);
                if (!body) {
                    fakeBody.style.background = "";
                    fakeBody.style.overflow = "hidden";
                    docOverflow = docElement.style.overflow;
                    docElement.style.overflow = "hidden";
                    docElement.appendChild(fakeBody);
                }

                ret = callback(div, rule);
                if (!body) {
                    fakeBody.parentNode.removeChild(fakeBody);
                    docElement.style.overflow = docOverflow;
                } else {
                    div.parentNode.removeChild(div);
                }

                return !!ret;

            },


            isEventSupported = (function() {

                var TAGNAMES = {
                    'select': "input",
                    'change': "input",
                    'submit': "form",
                    'reset': "form",
                    'error': "img",
                    'load': "img",
                    'abort': "img"
                };

                function isEventSupported(eventName, element) {

                    element = element || document.createElement(TAGNAMES[eventName] || "div");
                    eventName = "on" + eventName;

                    var isSupported = eventName in element;

                    if (!isSupported) {
                        if (!element.setAttribute) {
                            element = document.createElement("div");
                        }
                        if (element.setAttribute && element.removeAttribute) {
                            element.setAttribute(eventName, "");
                            isSupported = is(element[eventName], "function");

                            if (!is(element[eventName], "undefined")) {
                                element[eventName] = undefined;
                            }
                            element.removeAttribute(eventName);
                        }
                    }

                    element = null;
                    return isSupported;
                }

                return isEventSupported;
            })(),


            _hasOwnProperty = ({}).hasOwnProperty,
            hasOwnProp;

        if (!is(_hasOwnProperty, "undefined") && !is(_hasOwnProperty.call, "undefined")) {
            hasOwnProp = function(object, property) {
                return _hasOwnProperty.call(object, property);
            };
        } else {
            hasOwnProp = function(object, property) {
                return ((property in object) && is(object.constructor.prototype[property], "undefined"));
            };
        }


        if (!Function.prototype.bind) {
            Function.prototype.bind = function bind(that) {

                var target = this;

                if (typeof target != "function") {
                    throw new TypeError();
                }

                var args = slice.call(arguments, 1),
                    bound = function() {

                        if (this instanceof bound) {

                            var F = function() {};
                            F.prototype = target.prototype;
                            var self = new F();

                            var result = target.apply(
                                self,
                                args.concat(slice.call(arguments))
                            );
                            if (Object(result) === result) {
                                return result;
                            }
                            return self;

                        } else {

                            return target.apply(
                                that,
                                args.concat(slice.call(arguments))
                            );

                        }

                    };

                return bound;
            };
        }

        function setCss(str) {
            mStyle.cssText = str;
        }

        function setCssAll(str1, str2) {
            return setCss(prefixes.join(str1 + ";") + (str2 || ""));
        }

        function is(obj, type) {
            return typeof obj === type;
        }

        function contains(str, substr) {
            return !!~("" + str).indexOf(substr);
        }

        function testProps(props, prefixed) {
            for (var i in props) {
                var prop = props[i];
                if (!contains(prop, "-") && mStyle[prop] !== undefined) {
                    return prefixed == "pfx" ? prop : true;
                }
            }
            return false;
        }

        function testDOMProps(props, obj, elem) {
            for (var i in props) {
                var item = obj[props[i]];
                if (item !== undefined) {

                    if (elem === false) return props[i];

                    if (is(item, "function")) {
                        return item.bind(elem || obj);
                    }

                    return item;
                }
            }
            return false;
        }

        function testPropsAll(prop, prefixed, elem) {

            var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
                props = (prop + " " + cssomPrefixes.join(ucProp + " ") + ucProp).split(" ");

            if (is(prefixed, "string") || is(prefixed, "undefined")) {
                return testProps(props, prefixed);

            } else {
                props = (prop + " " + (domPrefixes).join(ucProp + " ") + ucProp).split(" ");
                return testDOMProps(props, prefixed, elem);
            }
        }


        tests["canvas"] = function() {
            var elem = document.createElement("canvas");
            return !!(elem.getContext && elem.getContext("2d"));
        };

        tests["canvastext"] = function() {
            return !!(Modernizr["canvas"] &&
                is(document.createElement("canvas").getContext("2d").fillText, "function"));
        };
        tests["touch"] = function() {
            var bool;

            if (("ontouchstart" in window) || window.DocumentTouch && document instanceof DocumentTouch) {
                bool = true;
            } else {
                injectElementWithStyles(
                    ["@media (", prefixes.join("touch-enabled),("), mod, ")", "{#modernizr{top:9px;position:absolute}}"]
                    .join(""),
                    function(node) {
                        bool = node.offsetTop === 9;
                    });
            }

            return bool;
        };

        tests["draganddrop"] = function() {
            var div = document.createElement("div");
            return ("draggable" in div) || ("ondragstart" in div && "ondrop" in div);
        };


        tests["csstransforms"] = function() {
            return !!testPropsAll("transform");
        };


        tests["svg"] = function() {
            return !!document.createElementNS && !!document.createElementNS(ns.svg, "svg").createSVGRect;
        };

        tests["inlinesvg"] = function() {
            var div = document.createElement("div");
            div.innerHTML = "<svg/>";
            return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
        };


        tests["svgclippaths"] = function() {
            return !!document.createElementNS &&
                /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, "clipPath")));
        };

        for (var feature in tests) {
            if (hasOwnProp(tests, feature)) {
                featureName = feature.toLowerCase();
                Modernizr[featureName] = tests[feature]();

                classes.push((Modernizr[featureName] ? "" : "no-") + featureName);
            }
        }


        Modernizr.addTest = function(feature, test) {
            if (typeof feature == "object") {
                for (var key in feature) {
                    if (hasOwnProp(feature, key)) {
                        Modernizr.addTest(key, feature[key]);
                    }
                }
            } else {

                feature = feature.toLowerCase();

                if (Modernizr[feature] !== undefined) {
                    return Modernizr;
                }

                test = typeof test == "function" ? test() : test;

                if (typeof enableClasses !== "undefined" && enableClasses) {
                    docElement.className += " " + (test ? "" : "no-") + feature;
                }
                Modernizr[feature] = test;

            }

            return Modernizr;
        };


        setCss("");
        modElem = inputElem = null;


        Modernizr._version = version;

        Modernizr._prefixes = prefixes;
        Modernizr._domPrefixes = domPrefixes;
        Modernizr._cssomPrefixes = cssomPrefixes;


        Modernizr.hasEvent = isEventSupported;

        Modernizr.testProp = function(prop) {
            return testProps([prop]);
        };

        Modernizr.testAllProps = testPropsAll;


        Modernizr.testStyles = injectElementWithStyles;
        return Modernizr;

    })(window, window.document);
// data uri test.
// https://github.com/Modernizr/Modernizr/issues/14

// This test is asynchronous. Watch out.


// in IE7 in HTTPS this can cause a Mixed Content security popup. 
//  github.com/Modernizr/Modernizr/issues/362
// To avoid that you can create a new iframe and inject this.. perhaps..


    (function() {

        var datauri = new Image();


        datauri.onerror = function() {
            Modernizr.addTest("datauri", function() { return false; });
        };
        datauri.onload = function() {
            Modernizr.addTest("datauri", function() { return (datauri.width == 1 && datauri.height == 1); });
        };

        datauri.src = "";

    })();;

    // Attach to jQuery object
    $.Jcrop = Jcrop;

    $.Jcrop.supportsCanvas = Modernizr.canvas;
    $.Jcrop.supportsCanvasText = Modernizr.canvastext;
    $.Jcrop.supportsDragAndDrop = Modernizr.draganddrop;
    $.Jcrop.supportsDataURI = Modernizr.datauri;
    $.Jcrop.supportsSVG = Modernizr.svg;
    $.Jcrop.supportsInlineSVG = Modernizr.inlinesvg;
    $.Jcrop.supportsSVGClipPaths = Modernizr.svgclippaths;
    $.Jcrop.supportsCSSTransforms = Modernizr.csstransforms;
    $.Jcrop.supportsTouch = Modernizr.touch;

})(jQuery);
